#!/usr/bin/env python3

# Copyright (c) 2020-2021 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import pwd
import json
import pathlib
from ..._util import Util, CopiedAndSymlinkedDir
from ..._errors import ConfigError
from ..._game import GameBase, GameSpec, MatchContext
from ..._game_straits import GameStraitForWindows, GameStraitNeedConfigDir


class HomeSheepHome2(GameBase, GameStraitForWindows):

    def __init__(self, data_dir):
        GameBase.__init__(self, data_dir)

        self._addStrait(GameStraitNeedConfigDir())

        self._spec = GameSpec()
        if True:
            self._spec.windows_archs = [self.WindowsArch.WIN32]
            self._spec.windows_versions = [self.WindowsVersion.WINXP, "*"]

            self._spec.default_language = "en_US"
            self._spec.languages = [self._spec.default_language]

            self._spec.resolutions = _SettingsFile.VALID_RESOLUTIONS
            self._spec.can_fullscreen = True
            self._spec.can_windowed = True

    @property
    def spec(self):
        return self._spec

    @property
    def run_items(self):
        return ["main"]

    def get_title(self, lang):
        return "Home Sheep Home 2"

    def _prepare(self, context):
        self._context = context
        try:
            self._targetGameDirObj = CopiedAndSymlinkedDir(os.path.join(self._context.drive_c_dirpath, "Home Sheep Home 2"), [self._dataDir])
            self._targetGameDirObj.symlink("", recursive=True)
            self._targetGameDirObj.clear()

            self._cfgFnObj = _SettingsFile(self._context.drive_c_dirpath)
        except BaseException:
            self._cleanup(on_error=True)
            raise

    @MatchContext.edit_or_run
    def get_language(self):
        return self._spec.default_language

    @MatchContext.edit_or_run
    def is_music_enabled(self):
        return not self.self._cfgFnObj.musicMuted

    @MatchContext.edit_or_run
    def is_sound_enabled(self):
        return not self._cfgFnObj.soundsMuted

    @MatchContext.edit_or_run
    def set_language(self, language):
        assert language == self._spec.default_language

    @MatchContext.run
    def apply_resolution(self, resolution):
        self._cfgFnObj.setResolution(resolution)

    @MatchContext.run
    def apply_fullscreen_or_windowed(self, fullscreen_or_windowed):
        self._cfgFnObj.setWindowed(not fullscreen_or_windowed)

    @MatchContext.run
    def apply_graphics_level(self, value):
        self._cfgFnObj.setGraphicsLevel(value)

    def _commit(self):
        self._cfgFnObj.save()

    def _getRunInfo(self, item):
        assert not self._context.edit_or_run
        assert item == Util.listGetOnlyItem(self.run_items)

        return self.RunInfo("C:\\Home Sheep Home 2\\HomeSheepHome2.exe")

    def _cleanup(self, on_error=False):
        warns = []
        if not on_error:
            cfgFn2 = _SettingsFile(self._context.drive_c_dirpath)
            template = "You shouldn't %s while in the game, as this setting will be lost the next time you launch this game."
            if cfgFn2.windowed != self._cfgFnObj.windowed:
                warns.append(template % ("change the windowed/fullscreen mode"))
            if cfgFn2.screenWidth != self._cfgFnObj.screenWidth and cfgFn2.screenHeight != self._cfgFnObj.screenHeight:
                warns.append(template % ("change the resolution"))
            if cfgFn2.performance != self._cfgFnObj.performance:
                warns.append(template % ("change the graphics settting"))

        if hasattr(self, "_targetGameDirObj"):
            if not on_error:
                pass
            del self._targetGameDirObj
        if hasattr(self, "_context"):
            del self._context

        return warns


class _SettingsFile:

    VALID_RESOLUTIONS = ["640x480", "800x600", "1024x768", "1280x1024", "1600x1200", "2048x1536"]

    def __init__(self, driver_c_dirpath):
        username = pwd.getpwuid(os.geteuid()).pw_name

        self._path = os.path.join(driver_c_dirpath, "users", username, "AppData", "Roaming", "HomeSheepHome2", "Local Store", "settings.txt")
        self._ver = 19

        # default values
        self._timmyIsOne = False
        self._controlReverseMode = True
        self._developersCommentary = False
        self._windowed = False
        self._screenSize = 4                 # range                 FIXME
        self._screenWidth = 1600             # range                 FIXME
        self._screenHeight = 1200            # range                 FIXME
        self._appScale = 2.5                 # range                 FIXME
        self._uiScale = 0.6                  # range                 FIXME
        self._soundsMuted = False
        self._musicMuted = False
        self._performance = "recommended"    # range: []                     FIXME
        self._replaceBitmaps = True
        self._allowScreenShake = True
        self._allowClouds = True
        self._autoReduce = True
        self._quality = "high"               # range: ["low", "high"]        FIXME

        # loads from settings file
        if os.path.exists(self._path):
            with open(self._path) as f:
                allKeys = [
                    "version",
                    "timmyIsOne",
                    "controlReverseMode",
                    "developersCommentary",
                    "windowed",
                    "screenSize",
                    "screenWidth",
                    "screenHeight",
                    "appScale",
                    "uiScale",
                    "soundsMuted",
                    "musicMuted",
                    "performance",
                    "replaceBitmaps",
                    "allowScreenShake",
                    "allowClouds",
                    "autoReduce",
                    "quality",
                ]
                for line in f:
                    key, value = line.strip().split(":", 1)
                    key = key.strip()
                    value = value.strip()

                    if key == "version":
                        if int(value) != self._ver:
                            raise ConfigError("invalid version")
                    elif key == "timmyIsOne":
                        self._timmyIsOne = self._str2bool(value)
                    elif key == "controlReverseMode":
                        self._controlReverseMode = self._str2bool(value)
                    elif key == "developersCommentary":
                        self._developersCommentary = self._str2bool(value)
                    elif key == "windowed":
                        self._windowed = self._str2bool(value)
                    elif key == "screenSize":
                        self._screenSize = int(value)
                    elif key == "screenWidth":
                        self._screenWidth = int(value)
                    elif key == "screenHeight":
                        self._screenHeight = int(value)
                    elif key == "appScale":
                        self._appScale = float(value)
                    elif key == "uiScale":
                        self._uiScale = float(value)
                    elif key == "soundsMuted":
                        self._soundsMuted = self._str2bool(value)
                    elif key == "musicMuted":
                        self._musicMuted = self._str2bool(value)
                    elif key == "performance":
                        self._performance = value
                    elif key == "replaceBitmaps":
                        self._replaceBitmaps = self._str2bool(value)
                    elif key == "allowScreenShake":
                        self._allowScreenShake = self._str2bool(value)
                    elif key == "allowClouds":
                        self._allowClouds = self._str2bool(value)
                    elif key == "autoReduce":
                        self._autoReduce = self._str2bool(value)
                    elif key == "quality":
                        self._quality = value
                    else:
                        raise ConfigError("invalid key")

                    allKeys.remove(key)

                if len(allKeys) > 0:
                    raise ConfigError("missing key: %s" % (allKeys[0]))

        self._businessCheck()

    @property
    def windowed(self):
        return self._windowed

    @property
    def screenWidth(self):
        return self._screenWidth

    @property
    def screenHeight(self):
        return self._screenHeight

    @property
    def musicMuted(self):
        return self._musicMuted

    @property
    def soundsMuted(self):
        return self._soundsMuted

    @property
    def performance(self):
        return self._performance

    def setDevelopersCommentary(self, value):
        self._developersCommentary = bool(value)

    def setWindowed(self, value):
        self._windowed = bool(value)

    def setResolution(self, resolution):
        assert resolution in self.VALID_RESOLUTIONS

        xy = resolution.split("x")
        self._screenWidth = int(xy[0])
        self._screenHeight = int(xy[1])

    def setGraphicsLevel(self, level):
        if level == "highest":
            self._performance = "high"
            self._allowScreenShake = True
            self._allowClouds = True
            self._autoReduce = True
            self._quality = "high"
            return

        if level == "typical":
            self._performance = "recommended"
            self._allowScreenShake = True
            self._allowClouds = True
            self._autoReduce = True
            self._quality = "high"
            return

        if level == "medium":
            self._performance = "medium"
            self._allowScreenShake = True
            self._allowClouds = True
            self._autoReduce = False
            self._quality = "medium"
            return

        if level == "low":
            self._performance = "low"
            self._allowScreenShake = False
            self._allowClouds = False
            self._autoReduce = True
            self._quality = "medium"
            return

        if level == "minimum":
            self._performance = "minimum"
            self._allowScreenShake = False
            self._allowClouds = False
            self._autoReduce = False
            self._quality = "low"
            return

        assert False

    def save(self):
        self._businessCheck()

        buf = ""
        buf += "version:              %d\n" % (self._ver)
        buf += "timmyIsOne:           %s\n" % (self._bool2str(self._timmyIsOne))
        buf += "controlReverseMode:   %s\n" % (self._bool2str(self._controlReverseMode))
        buf += "developersCommentary: %s\n" % (self._bool2str(self._developersCommentary))
        buf += "windowed:             %s\n" % (self._bool2str(self._windowed))
        buf += "screenSize:           %d\n" % (self._screenSize)
        buf += "screenWidth:          %d\n" % (self._screenWidth)
        buf += "screenHeight:         %d\n" % (self._screenHeight)
        buf += "appScale:             %.1f\n" % (self._appScale)
        buf += "uiScale:              %.1f\n" % (self._uiScale)
        buf += "soundsMuted:          %s\n" % (self._bool2str(self._soundsMuted))
        buf += "musicMuted:           %s\n" % (self._bool2str(self._musicMuted))
        buf += "performance:          %s\n" % (self._performance)
        buf += "replaceBitmaps:       %s\n" % (self._bool2str(self._replaceBitmaps))
        buf += "allowScreenShake:     %s\n" % (self._bool2str(self._allowScreenShake))
        buf += "allowClouds:          %s\n" % (self._bool2str(self._allowClouds))
        buf += "autoReduce:           %s\n" % (self._bool2str(self._autoReduce))
        buf += "quality:              %s\n" % (self._quality)

        # the original game does this
        if os.path.exists(self._path):
            os.rename(self._path, self._path.replace(".txt", ".old"))

        os.makedirs(os.path.dirname(self._path), exist_ok=True)
        pathlib.Path(self._path).write_text(buf)

    def _businessCheck(self):
        pass
        # self.screenSize = 4                 # range
        # self.screenWidth = 1600             # range
        # self.screenHeight = 1200            # range
        # self.appScale = 2.5
        # self.uiScale = 0.6
        # self.performance = "recommended"    # range: []                     FIXME
        # self.quality = "high"               # range: ["low", "high"]        FIXME

    @staticmethod
    def _bool2str(value):
        return "true" if value else "false"

    @staticmethod
    def _str2bool(value):
        if value == "true":
            return True
        elif value == "false":
            return False
        else:
            raise ConfigError("invalid bool value")


class _SaveDatasFile:

    def __init__(self, driver_c_dirpath):
        username = pwd.getpwuid(os.geteuid()).pw_name

        self._path = os.path.join(driver_c_dirpath, "users", username, "AppData", "Roaming", "HomeSheepHome2", "Local Store", "savedata.txt")
        self._ver = 61

        # default values
        self._data = {
            "ver": self._ver,
            "defs": [
                {
                    "par": 1854,
                    "complete": False,
                    "brisk": 927,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 415,
                    "isBonus": False,
                    "dev": 355,
                    "stars": 0,
                    "title": "Level01",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 2820,
                    "complete": False,
                    "brisk": 1410,
                    "episode": "Underground",
                    "levelNum": 2,
                    "devAardman": 319,
                    "isBonus": False,
                    "dev": 286,
                    "stars": 0,
                    "title": "Level02",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2733,
                    "complete": False,
                    "brisk": 1367,
                    "episode": "Underground",
                    "levelNum": 3,
                    "devAardman": 245,
                    "isBonus": False,
                    "dev": 213,
                    "stars": 0,
                    "title": "Level03",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3075,
                    "complete": False,
                    "brisk": 1538,
                    "episode": "Underground",
                    "levelNum": 4,
                    "devAardman": 746,
                    "isBonus": False,
                    "dev": 576,
                    "stars": 0,
                    "title": "Level04",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3096,
                    "complete": False,
                    "brisk": 1548,
                    "episode": "Underground",
                    "levelNum": 5,
                    "devAardman": 696,
                    "isBonus": False,
                    "dev": 514,
                    "stars": 0,
                    "title": "Level05",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5040,
                    "complete": False,
                    "brisk": 2520,
                    "episode": "Underground",
                    "levelNum": 6,
                    "devAardman": 1210,
                    "isBonus": False,
                    "dev": 473,
                    "stars": 0,
                    "title": "Level06",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2481,
                    "complete": False,
                    "brisk": 1241,
                    "episode": "Underground",
                    "levelNum": 7,
                    "devAardman": 661,
                    "isBonus": False,
                    "dev": 559,
                    "stars": 0,
                    "title": "Level07",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1938,
                    "complete": False,
                    "brisk": 969,
                    "episode": "Underground",
                    "levelNum": 8,
                    "devAardman": 404,
                    "isBonus": False,
                    "dev": 352,
                    "stars": 0,
                    "title": "Level08",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2766,
                    "complete": False,
                    "brisk": 1383,
                    "episode": "Underground",
                    "levelNum": 9,
                    "devAardman": 375,
                    "isBonus": False,
                    "dev": 301,
                    "stars": 0,
                    "title": "Level09",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3810,
                    "complete": False,
                    "brisk": 1905,
                    "episode": "Underground",
                    "levelNum": 10,
                    "devAardman": 797,
                    "isBonus": False,
                    "dev": 603,
                    "stars": 0,
                    "title": "Level10",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 4407,
                    "complete": False,
                    "brisk": 2204,
                    "episode": "Underground",
                    "levelNum": 11,
                    "devAardman": 1014,
                    "isBonus": False,
                    "dev": 694,
                    "stars": 0,
                    "title": "Level11",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5268,
                    "complete": False,
                    "brisk": 2634,
                    "episode": "Underground",
                    "levelNum": 12,
                    "devAardman": 1091,
                    "isBonus": False,
                    "dev": 825,
                    "stars": 0,
                    "title": "Level12",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2439,
                    "complete": False,
                    "brisk": 1220,
                    "episode": "Underground",
                    "levelNum": 13,
                    "devAardman": 575,
                    "isBonus": False,
                    "dev": 449,
                    "stars": 0,
                    "title": "Level13",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2397,
                    "complete": False,
                    "brisk": 1199,
                    "episode": "Underground",
                    "levelNum": 14,
                    "devAardman": 642,
                    "isBonus": False,
                    "dev": 309,
                    "stars": 0,
                    "title": "Level14",
                    "autoReduce": True,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3489,
                    "complete": False,
                    "brisk": 1745,
                    "episode": "Underground",
                    "levelNum": 15,
                    "devAardman": 267,
                    "isBonus": False,
                    "dev": 211,
                    "stars": 0,
                    "title": "Level15",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1425,
                    "complete": False,
                    "brisk": 713,
                    "episode": "London",
                    "levelNum": 1,
                    "devAardman": 350,
                    "isBonus": False,
                    "dev": 255,
                    "stars": 0,
                    "title": "Level16",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 2733,
                    "complete": False,
                    "brisk": 1367,
                    "episode": "London",
                    "levelNum": 2,
                    "devAardman": 486,
                    "isBonus": False,
                    "dev": 464,
                    "stars": 0,
                    "title": "Level17",
                    "autoReduce": True,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1488,
                    "complete": False,
                    "brisk": 744,
                    "episode": "London",
                    "levelNum": 3,
                    "devAardman": 336,
                    "isBonus": False,
                    "dev": 234,
                    "stars": 0,
                    "title": "Level18",
                    "autoReduce": True,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2082,
                    "complete": False,
                    "brisk": 1041,
                    "episode": "London",
                    "levelNum": 4,
                    "devAardman": 455,
                    "isBonus": False,
                    "dev": 283,
                    "stars": 0,
                    "title": "Level19",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1716,
                    "complete": False,
                    "brisk": 858,
                    "episode": "London",
                    "levelNum": 5,
                    "devAardman": 389,
                    "isBonus": False,
                    "dev": 243,
                    "stars": 0,
                    "title": "Level20",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3222,
                    "complete": False,
                    "brisk": 1611,
                    "episode": "London",
                    "levelNum": 6,
                    "devAardman": 439,
                    "isBonus": False,
                    "dev": 300,
                    "stars": 0,
                    "title": "Level21",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3885,
                    "complete": False,
                    "brisk": 1943,
                    "episode": "London",
                    "levelNum": 7,
                    "devAardman": 699,
                    "isBonus": False,
                    "dev": 524,
                    "stars": 0,
                    "title": "Level22",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 6711,
                    "complete": False,
                    "brisk": 3356,
                    "episode": "London",
                    "levelNum": 8,
                    "devAardman": 1578,
                    "isBonus": False,
                    "dev": 994,
                    "stars": 0,
                    "title": "Level23",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5883,
                    "complete": False,
                    "brisk": 2942,
                    "episode": "London",
                    "levelNum": 9,
                    "devAardman": 720,
                    "isBonus": False,
                    "dev": 473,
                    "stars": 0,
                    "title": "Level24",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5133,
                    "complete": False,
                    "brisk": 2567,
                    "episode": "London",
                    "levelNum": 10,
                    "devAardman": 425,
                    "isBonus": False,
                    "dev": 149,
                    "stars": 0,
                    "title": "Level25",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1509,
                    "complete": False,
                    "brisk": 755,
                    "episode": "London",
                    "levelNum": 11,
                    "devAardman": 307,
                    "isBonus": False,
                    "dev": 301,
                    "stars": 0,
                    "title": "Level26",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2004,
                    "complete": False,
                    "brisk": 1002,
                    "episode": "London",
                    "levelNum": 12,
                    "devAardman": 345,
                    "isBonus": False,
                    "dev": 307,
                    "stars": 0,
                    "title": "Level27",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5229,
                    "complete": False,
                    "brisk": 2615,
                    "episode": "London",
                    "levelNum": 13,
                    "devAardman": 785,
                    "isBonus": False,
                    "dev": 542,
                    "stars": 0,
                    "title": "Level28",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3396,
                    "complete": False,
                    "brisk": 1698,
                    "episode": "London",
                    "levelNum": 14,
                    "devAardman": 637,
                    "isBonus": False,
                    "dev": 618,
                    "stars": 0,
                    "title": "Level29",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3651,
                    "complete": False,
                    "brisk": 1826,
                    "episode": "London",
                    "levelNum": 15,
                    "devAardman": 402,
                    "isBonus": False,
                    "dev": 354,
                    "stars": 0,
                    "title": "Level30",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1428,
                    "complete": False,
                    "brisk": 714,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 310,
                    "isBonus": False,
                    "dev": 309,
                    "stars": 0,
                    "title": "Level31",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 2241,
                    "complete": False,
                    "brisk": 1121,
                    "episode": "Space",
                    "levelNum": 2,
                    "devAardman": 415,
                    "isBonus": False,
                    "dev": 345,
                    "stars": 0,
                    "title": "Level32",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1479,
                    "complete": False,
                    "brisk": 740,
                    "episode": "Space",
                    "levelNum": 3,
                    "devAardman": 179,
                    "isBonus": False,
                    "dev": 153,
                    "stars": 0,
                    "title": "Level33",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2202,
                    "complete": False,
                    "brisk": 1101,
                    "episode": "Space",
                    "levelNum": 4,
                    "devAardman": 565,
                    "isBonus": False,
                    "dev": 385,
                    "stars": 0,
                    "title": "Level34",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 1203,
                    "complete": False,
                    "brisk": 602,
                    "episode": "Space",
                    "levelNum": 5,
                    "devAardman": 234,
                    "isBonus": False,
                    "dev": 214,
                    "stars": 0,
                    "title": "Level35",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2217,
                    "complete": False,
                    "brisk": 1109,
                    "episode": "Space",
                    "levelNum": 6,
                    "devAardman": 419,
                    "isBonus": False,
                    "dev": 364,
                    "stars": 0,
                    "title": "Level36",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3933,
                    "complete": False,
                    "brisk": 1967,
                    "episode": "Space",
                    "levelNum": 7,
                    "devAardman": 1152,
                    "isBonus": False,
                    "dev": 921,
                    "stars": 0,
                    "title": "Level37",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3207,
                    "complete": False,
                    "brisk": 1604,
                    "episode": "Space",
                    "levelNum": 8,
                    "devAardman": 837,
                    "isBonus": False,
                    "dev": 673,
                    "stars": 0,
                    "title": "Level38",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3591,
                    "complete": False,
                    "brisk": 1796,
                    "episode": "Space",
                    "levelNum": 9,
                    "devAardman": 863,
                    "isBonus": False,
                    "dev": 818,
                    "stars": 0,
                    "title": "Level39",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5757,
                    "complete": False,
                    "brisk": 2879,
                    "episode": "Space",
                    "levelNum": 10,
                    "devAardman": 1503,
                    "isBonus": False,
                    "dev": 1280,
                    "stars": 0,
                    "title": "Level40",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 5595,
                    "complete": False,
                    "brisk": 2798,
                    "episode": "Space",
                    "levelNum": 11,
                    "devAardman": 1127,
                    "isBonus": False,
                    "dev": 859,
                    "stars": 0,
                    "title": "Level41",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 4428,
                    "complete": False,
                    "brisk": 2214,
                    "episode": "Space",
                    "levelNum": 12,
                    "devAardman": 907,
                    "isBonus": False,
                    "dev": 907,
                    "stars": 0,
                    "title": "Level42",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 8190,
                    "complete": False,
                    "brisk": 4095,
                    "episode": "Space",
                    "levelNum": 13,
                    "devAardman": 2049,
                    "isBonus": False,
                    "dev": 1429,
                    "stars": 0,
                    "title": "Level43",
                    "autoReduce": True,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3477,
                    "complete": False,
                    "brisk": 1739,
                    "episode": "Space",
                    "levelNum": 14,
                    "devAardman": 792,
                    "isBonus": False,
                    "dev": 432,
                    "stars": 0,
                    "title": "Level44",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3066,
                    "complete": False,
                    "brisk": 1533,
                    "episode": "Space",
                    "levelNum": 15,
                    "devAardman": 851,
                    "isBonus": False,
                    "dev": 758,
                    "stars": 0,
                    "title": "Level45",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 15,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 996,
                    "complete": False,
                    "brisk": 498,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 276,
                    "isBonus": True,
                    "dev": 276,
                    "stars": 0,
                    "title": "Level46",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3555,
                    "complete": False,
                    "brisk": 1778,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 788,
                    "isBonus": True,
                    "dev": 788,
                    "stars": 0,
                    "title": "Level47",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3279,
                    "complete": False,
                    "brisk": 1640,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 440,
                    "isBonus": True,
                    "dev": 440,
                    "stars": 0,
                    "title": "Level48",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 4338,
                    "complete": False,
                    "brisk": 2169,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 960,
                    "isBonus": True,
                    "dev": 1167,
                    "stars": 0,
                    "title": "Level49",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2751,
                    "complete": False,
                    "brisk": 1376,
                    "episode": "Underground",
                    "levelNum": 1,
                    "devAardman": 466,
                    "isBonus": True,
                    "dev": 466,
                    "stars": 0,
                    "title": "Level50",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2973,
                    "complete": False,
                    "brisk": 1487,
                    "episode": "London",
                    "levelNum": 1,
                    "devAardman": 763,
                    "isBonus": True,
                    "dev": 763,
                    "stars": 0,
                    "title": "Level51",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 7017,
                    "complete": False,
                    "brisk": 3509,
                    "episode": "London",
                    "levelNum": 1,
                    "devAardman": 1944,
                    "isBonus": True,
                    "dev": 1944,
                    "stars": 0,
                    "title": "Level52",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2562,
                    "complete": False,
                    "brisk": 1281,
                    "episode": "London",
                    "levelNum": 1,
                    "devAardman": 714,
                    "isBonus": True,
                    "dev": 714,
                    "stars": 0,
                    "title": "Level53",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3045,
                    "complete": False,
                    "brisk": 1523,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 660,
                    "isBonus": True,
                    "dev": 660,
                    "stars": 0,
                    "title": "Level54",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3804,
                    "complete": False,
                    "brisk": 1902,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 920,
                    "isBonus": True,
                    "dev": 1220,
                    "stars": 0,
                    "title": "Level55",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 3582,
                    "complete": False,
                    "brisk": 1791,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 336,
                    "isBonus": True,
                    "dev": 336,
                    "stars": 0,
                    "title": "Level56",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2154,
                    "complete": False,
                    "brisk": 1077,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 566,
                    "isBonus": True,
                    "dev": 566,
                    "stars": 0,
                    "title": "Level57",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 2826,
                    "complete": False,
                    "brisk": 1413,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 710,
                    "isBonus": True,
                    "dev": 710,
                    "stars": 0,
                    "title": "Level58",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 9105,
                    "complete": False,
                    "brisk": 4553,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 1549,
                    "isBonus": True,
                    "dev": 1549,
                    "stars": 0,
                    "title": "Level59",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 6630,
                    "complete": False,
                    "brisk": 3315,
                    "episode": "Space",
                    "levelNum": 1,
                    "devAardman": 1697,
                    "isBonus": True,
                    "dev": 1697,
                    "stars": 0,
                    "title": "Level60",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": True,
                },
                {
                    "par": 993,
                    "complete": False,
                    "brisk": 497,
                    "episode": "Training",
                    "levelNum": 1,
                    "devAardman": 210,
                    "isBonus": False,
                    "dev": 0,
                    "stars": 0,
                    "title": "Level61",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 3,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 1617,
                    "complete": False,
                    "brisk": 809,
                    "episode": "Training",
                    "levelNum": 2,
                    "devAardman": 333,
                    "isBonus": False,
                    "dev": 0,
                    "stars": 0,
                    "title": "Level62",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 3,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 1809,
                    "complete": False,
                    "brisk": 905,
                    "episode": "Training",
                    "levelNum": 3,
                    "devAardman": 447,
                    "isBonus": False,
                    "dev": 0,
                    "stars": 0,
                    "title": "Level63",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 3,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
                {
                    "par": 4332,
                    "complete": False,
                    "brisk": 2166,
                    "episode": "Credits",
                    "levelNum": 1,
                    "devAardman": 762,
                    "isBonus": False,
                    "dev": 0,
                    "stars": 0,
                    "title": "Level64",
                    "autoReduce": False,
                    "best": 0,
                    "levelTotal": 1,
                    "available": True,
                    "attempts": 0,
                    "locked": False,
                },
            ],
            "collectables": [
                {"id": "secret09", "got": False},
                {"id": "gemU10a", "got": False},
                {"id": "gemU10b", "got": False},
                {"id": "gemS39b", "got": False},
                {"id": "gemU11a", "got": False},
                {"id": "cake39", "got": False},
                {"id": "gemU11b", "got": False},
                {"id": "secret39", "got": False},
                {"id": "cake11", "got": False},
                {"id": "gemS40a", "got": False},
                {"id": "secret11", "got": False},
                {"id": "gemS40b", "got": False},
                {"id": "gemU12a", "got": False},
                {"id": "bonus55", "got": False},
                {"id": "gemU12b", "got": False},
                {"id": "secret40", "got": False},
                {"id": "cake12", "got": False},
                {"id": "gemS41a", "got": False},
                {"id": "secret12", "got": False},
                {"id": "gemS41b", "got": False},
                {"id": "gemU13a", "got": False},
                {"id": "bonus56", "got": False},
                {"id": "gemU13b", "got": False},
                {"id": "secret41", "got": False},
                {"id": "cake13", "got": False},
                {"id": "gemS42a", "got": False},
                {"id": "gemU14a", "got": False},
                {"id": "gemS42b", "got": False},
                {"id": "bonus48", "got": False},
                {"id": "gemS42c", "got": False},
                {"id": "gemU15a", "got": False},
                {"id": "cake42", "got": False},
                {"id": "cake15", "got": False},
                {"id": "secret42", "got": False},
                {"id": "gemL16a", "got": False},
                {"id": "gemS43a", "got": False},
                {"id": "gemL16b", "got": False},
                {"id": "gemS43b", "got": False},
                {"id": "gemL17a", "got": False},
                {"id": "gemS44a", "got": False},
                {"id": "gemL17b", "got": False},
                {"id": "cake44", "got": False},
                {"id": "gemL18a", "got": False},
                {"id": "gemS45a", "got": False},
                {"id": "gemL18b", "got": False},
                {"id": "gemS45b", "got": False},
                {"id": "cake18", "got": False},
                {"id": "secret18", "got": False},
                {"id": "gemL19a", "got": False},
                {"id": "gemL20a", "got": False},
                {"id": "gemL20b", "got": False},
                {"id": "cake20", "got": False},
                {"id": "secret20", "got": False},
                {"id": "gemL21a", "got": False},
                {"id": "gemL21b", "got": False},
                {"id": "cake21", "got": False},
                {"id": "secret21", "got": False},
                {"id": "gemL22a", "got": False},
                {"id": "bonus49", "got": False},
                {"id": "secret22", "got": False},
                {"id": "gemL23a", "got": False},
                {"id": "gemL23b", "got": False},
                {"id": "bonus50", "got": False},
                {"id": "secret23", "got": False},
                {"id": "bonus60", "got": False},
                {"id": "gemL25a", "got": False},
                {"id": "cake64", "got": False},
                {"id": "gemL25b", "got": False},
                {"id": "gemL24a", "got": False},
                {"id": "gemL26a", "got": False},
                {"id": "gemL26b", "got": False},
                {"id": "gemL26c", "got": False},
                {"id": "gemL27a", "got": False},
                {"id": "gemL27b", "got": False},
                {"id": "gemL28a", "got": False},
                {"id": "gemU1a", "got": False},
                {"id": "gemL28b", "got": False},
                {"id": "gemU1b", "got": False},
                {"id": "bonus57", "got": False},
                {"id": "gemU2a", "got": False},
                {"id": "gemL29a", "got": False},
                {"id": "gemU2b", "got": False},
                {"id": "gemL29b", "got": False},
                {"id": "cake02", "got": False},
                {"id": "gemL29c", "got": False},
                {"id": "secret02", "got": False},
                {"id": "gemL30a", "got": False},
                {"id": "gemU3a", "got": False},
                {"id": "gemL30b", "got": False},
                {"id": "gemU3b", "got": False},
                {"id": "gemS31a", "got": False},
                {"id": "bonus46", "got": False},
                {"id": "gemS32a", "got": False},
                {"id": "cake03", "got": False},
                {"id": "gemS32b", "got": False},
                {"id": "secret03a", "got": False},
                {"id": "gemS33a", "got": False},
                {"id": "secret03b", "got": False},
                {"id": "secret33", "got": False},
                {"id": "gemU4a", "got": False},
                {"id": "gemS34a", "got": False},
                {"id": "gemU4b", "got": False},
                {"id": "gemS34b", "got": False},
                {"id": "gemU4c", "got": False},
                {"id": "bonus51", "got": False},
                {"id": "gemU5a", "got": False},
                {"id": "secret34", "got": False},
                {"id": "gemU5b", "got": False},
                {"id": "gemS35a", "got": False},
                {"id": "gemU5c", "got": False},
                {"id": "gemS35b", "got": False},
                {"id": "bonus47", "got": False},
                {"id": "gemS35c", "got": False},
                {"id": "secret05", "got": False},
                {"id": "cake35", "got": False},
                {"id": "gemU6a", "got": False},
                {"id": "secret35", "got": False},
                {"id": "gemU6b", "got": False},
                {"id": "gemS36a", "got": False},
                {"id": "gemU6c", "got": False},
                {"id": "gemS36b", "got": False},
                {"id": "bonus58", "got": False},
                {"id": "bonus52", "got": False},
                {"id": "gemU7a", "got": False},
                {"id": "secret36", "got": False},
                {"id": "gemU7b", "got": False},
                {"id": "gemS37a", "got": False},
                {"id": "gemU7c", "got": False},
                {"id": "gemS37b", "got": False},
                {"id": "cake07", "got": False},
                {"id": "bonus53", "got": False},
                {"id": "gemU8a", "got": False},
                {"id": "secret37", "got": False},
                {"id": "gemU8b", "got": False},
                {"id": "gemS38a", "got": False},
                {"id": "gemU8c", "got": False},
                {"id": "gemS38b", "got": False},
                {"id": "cake08", "got": False},
                {"id": "bonus54", "got": False},
                {"id": "secret08", "got": False},
                {"id": "secret38", "got": False},
                {"id": "gemU9a", "got": False},
                {"id": "gemS39a", "got": False},
                {"id": "bonus59", "got": False},
            ],
            "achievements": [
                {"id": "miscCrossPromo", "got": False},
                {"id": "collect12Socks", "got": False},
                {"id": "miscSubmitScore", "got": False},
                {"id": "collect24Socks", "got": False},
                {"id": "stars1", "got": False},
                {"id": "stars5", "got": False},
                {"id": "stars30", "got": False},
                {"id": "starsAll", "got": False},
                {"id": "starsDev", "got": False},
                {"id": "collect2Socks", "got": False},
                {"id": "collect12SocksWeb", "got": False},
                {"id": "progressUnderground_1", "got": False},
                {"id": "collect50Socks", "got": False},
                {"id": "progressUnderground_5", "got": False},
                {"id": "collect75Socks", "got": False},
                {"id": "progressUnderground_10", "got": False},
                {"id": "collectAllSocks", "got": False},
                {"id": "progressUnderground_15", "got": False},
                {"id": "collect1Cake", "got": False},
                {"id": "progressLondon_1", "got": False},
                {"id": "collect1Secret", "got": False},
                {"id": "progressLondon_5", "got": False},
                {"id": "collect1Joystick", "got": False},
                {"id": "progressLondon_10", "got": False},
                {"id": "collectAllCakes", "got": False},
                {"id": "progressLondon_15", "got": False},
                {"id": "collectAllSecrets", "got": False},
                {"id": "progressSpace_1", "got": False},
                {"id": "collectAllJoysticks", "got": False},
                {"id": "progressSpace_5", "got": False},
                {"id": "collectAllCollectables", "got": False},
                {"id": "progressSpace_10", "got": False},
                {"id": "miscOneHundredPercent", "got": False},
                {"id": "progressSpace_15", "got": False},
                {"id": "progressTraining1", "got": False},
                {"id": "progressTraining2", "got": False},
                {"id": "progressTraining3", "got": False},
                {"id": "progressBonus1", "got": False},
                {"id": "progressBonus5", "got": False},
                {"id": "progressBonus10", "got": False},
                {"id": "progressBonusAll", "got": False},
                {"id": "miscCredits", "got": False},
                {"id": "miscRoastLamb", "got": False},
                {"id": "miscBringLuggage", "got": False},
                {"id": "miscCameraDestroy", "got": False},
                {"id": "miscCameraJump", "got": False},
                {"id": "progressAllEpisodes", "got": False},
                {"id": "miscUndamagedChimneys", "got": False},
                {"id": "miscTwoParachutes", "got": False},
                {"id": "miscNoExplosives", "got": False},
                {"id": "miscSaveExplosives", "got": False},
                {"id": "miscTwoTeleports", "got": False},
                {"id": "miscSingleSwap", "got": False},
                {"id": "miscSurfTheVan", "got": False},
                {"id": "miscUnchangedTime", "got": False},
                {"id": "miscSheepJuggling", "got": False},
                {"id": "miscNoFootball", "got": False},
            ],
            "seenComments": [
            ],
            "hacksTried": [
            ],
            "val": None,
        }

        # loads from settings file
        if os.path.exists(self._path):
            with open(self._path) as f:
                self._data = json.loads(f.read())

        self._businessCheck()

    def save(self):
        self._businessCheck()

        # the original game does this
        if os.path.exists(self._path):
            os.rename(self._path, self._path.replace(".txt", ".old"))

        os.makedirs(os.path.dirname(self._path), exist_ok=True)
        with open(self._path, "w") as f:
            json.dump(self._data, f, indent=4)

    def _businessCheck(self):
        pass
